/*
 * Copyright (C) 2015 Square, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package retrofit2;

import okhttp3.MediaType;
import okhttp3.Request;
import okhttp3.ResponseBody;
import okio.*;

import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import java.io.IOException;
import java.util.Objects;

import static retrofit2.Utils.throwIfFatal;

/**
 * Call接口的实现，使用OkHttp进行请求，并进行结果回调的封装类，具有数据和模型的转换能力
 */
final class OkHttpCall<T> implements Call<T> {
    private final RequestFactory requestFactory;
    private final Object[] args;
    private final okhttp3.Call.Factory callFactory;
    private final Converter<ResponseBody, T> responseConverter;

    private volatile boolean canceled;

    @GuardedBy("this")
    private @Nullable
    okhttp3.Call rawCall;

    @GuardedBy("this") // Either a RuntimeException, non-fatal Error, or IOException.
    private @Nullable
    Throwable creationFailure;

    @GuardedBy("this")
    private boolean executed;

    OkHttpCall(
            RequestFactory requestFactory,
            Object[] args,
            okhttp3.Call.Factory callFactory,
            Converter<ResponseBody, T> responseConverter) {
        this.requestFactory = requestFactory;
        this.args = args;
        this.callFactory = callFactory;
        this.responseConverter = responseConverter;
    }

    @SuppressWarnings("CloneDoesntCallSuperClone") // We are a final type & this saves clearing state.
    @Override
    public OkHttpCall<T> clone() {
        return new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
    }

    @Override
    public synchronized Request request() {
        try {
            return getRawCall().request();
        } catch (IOException e) {
            throw new RuntimeException("Unable to create request.", e);
        }
    }

    @Override
    public synchronized Timeout timeout() {
        try {
            return getRawCall().timeout();
        } catch (IOException e) {
            throw new RuntimeException("Unable to create call.", e);
        }
    }

    /**
     * 获取真实请求的实例，就是OkHttp的Call
     */
    @GuardedBy("this")
    private okhttp3.Call getRawCall() throws IOException {
        okhttp3.Call call = rawCall;
        //已经生成过了，直接返回
        if (call != null) {
            return call;
        }

        //不是第一次进行接口请求，并且发生错误，把错误再次抛出
        if (creationFailure != null) {
            if (creationFailure instanceof IOException) {
                throw (IOException) creationFailure;
            } else if (creationFailure instanceof RuntimeException) {
                throw (RuntimeException) creationFailure;
            } else {
                throw (Error) creationFailure;
            }
        }

        //创建OkHttp的Call对象
        try {
            return rawCall = createRawCall();
        } catch (RuntimeException | Error | IOException e) {
            throwIfFatal(e); // Do not assign a fatal error to creationFailure.
            creationFailure = e;
            throw e;
        }
    }

    @Override
    public void enqueue(final Callback<T> callback) {
        Objects.requireNonNull(callback, "callback == null");

        okhttp3.Call call;
        Throwable failure;

        synchronized (this) {
            //调用了一次enqueue()异步请求，不能再次调用
            if (executed) throw new IllegalStateException("Already executed.");
            executed = true;

            call = rawCall;
            failure = creationFailure;
            //创建OkHttp的Call对象
            if (call == null && failure == null) {
                try {
                    call = rawCall = createRawCall();
                } catch (Throwable t) {
                    throwIfFatal(t);
                    failure = creationFailure = t;
                }
            }
        }

        //失败了，回调外部
        if (failure != null) {
            callback.onFailure(this, failure);
            return;
        }

        //取消了任务，对真实请求做取消
        if (canceled) {
            call.cancel();
        }

        //发起异步请求
        call.enqueue(new okhttp3.Callback() {
            @Override
            public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
                Response<T> response;
                try {
                    //执行，并解析响应，统一返回Retrofit的Response对象
                    response = parseResponse(rawResponse);
                } catch (Throwable e) {
                    //解析失败，回调外部
                    throwIfFatal(e);
                    callFailure(e);
                    return;
                }
                //解析成功，回调外部
                try {
                    callback.onResponse(OkHttpCall.this, response);
                } catch (Throwable t) {
                    throwIfFatal(t);
                    t.printStackTrace(); // TODO this is not great
                }
            }

            @Override
            public void onFailure(okhttp3.Call call, IOException e) {
                //请求失败，回调外部
                callFailure(e);
            }

            /**
             * 回调失败
             */
            private void callFailure(Throwable e) {
                try {
                    callback.onFailure(OkHttpCall.this, e);
                } catch (Throwable t) {
                    throwIfFatal(t);
                    t.printStackTrace(); // TODO this is not great
                }
            }
        });
    }

    @Override
    public synchronized boolean isExecuted() {
        return executed;
    }

    @Override
    public Response<T> execute() throws IOException {
        okhttp3.Call call;

        //同步请求
        synchronized (this) {
            if (executed) throw new IllegalStateException("Already executed.");
            executed = true;

            call = getRawCall();
        }

        //如果被取消，则取消任务
        if (canceled) {
            call.cancel();
        }

        //执行，并解析响应，统一返回Retrofit的Response对象
        return parseResponse(call.execute());
    }

    /**
     * 创建OkHttp3的Call对象
     */
    private okhttp3.Call createRawCall() throws IOException {
        okhttp3.Call call = callFactory.newCall(requestFactory.create(args));
        //设置的OkHttpClient，不能返回null
        if (call == null) {
            throw new NullPointerException("Call.Factory returned null.");
        }
        return call;
    }

    /**
     * 执行，并解析响应，统一返回Retrofit的Response对象
     */
    Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
        ResponseBody rawBody = rawResponse.body();

        //移除真实请求的Body，方便传递
        rawResponse = rawResponse
                .newBuilder()
                .body(new NoContentResponseBody(rawBody.contentType(), rawBody.contentLength()))
                .build();

        int code = rawResponse.code();
        //响应码不为正常的
        if (code < 200 || code >= 300) {
            try {
                ResponseBody bufferedBody = Utils.buffer(rawBody);
                //生成错误的Response对象
                return Response.error(bufferedBody, rawResponse);
            } finally {
                //关闭流
                rawBody.close();
            }
        }

        //204：请求成功了，但是没有相应体
        //205：请求成功了，重置表单，一般WebView才有
        if (code == 204 || code == 205) {
            rawBody.close();
            //由于没有响应体，直接返回成功
            return Response.success(null, rawResponse);
        }

        //包装一层，捕获异常
        ExceptionCatchingResponseBody catchingBody = new ExceptionCatchingResponseBody(rawBody);
        try {
            //把ResponseBody转换具体的泛型模型类
            T body = responseConverter.convert(catchingBody);
            //返回Response
            return Response.success(body, rawResponse);
        } catch (RuntimeException e) {
            // If the underlying source threw an exception, propagate that rather than indicating it was
            // a runtime exception.
            catchingBody.throwIfCaught();
            throw e;
        }
    }

    @Override
    public void cancel() {
        canceled = true;

        okhttp3.Call call;
        synchronized (this) {
            call = rawCall;
        }
        //取消任务
        if (call != null) {
            call.cancel();
        }
    }

    @Override
    public boolean isCanceled() {
        if (canceled) {
            return true;
        }
        synchronized (this) {
            return rawCall != null && rawCall.isCanceled();
        }
    }

    static final class NoContentResponseBody extends ResponseBody {
        private final @Nullable
        MediaType contentType;
        private final long contentLength;

        NoContentResponseBody(@Nullable MediaType contentType, long contentLength) {
            this.contentType = contentType;
            this.contentLength = contentLength;
        }

        @Override
        public MediaType contentType() {
            return contentType;
        }

        @Override
        public long contentLength() {
            return contentLength;
        }

        @Override
        public BufferedSource source() {
            throw new IllegalStateException("Cannot read raw response body of a converted body.");
        }
    }

    /**
     * 装饰器，捕获异常的ResponseBody
     */
    static final class ExceptionCatchingResponseBody extends ResponseBody {
        private final ResponseBody delegate;
        private final BufferedSource delegateSource;
        @Nullable
        IOException thrownException;

        ExceptionCatchingResponseBody(ResponseBody delegate) {
            this.delegate = delegate;
            this.delegateSource = Okio.buffer(
                    new ForwardingSource(delegate.source()) {
                        @Override
                        public long read(Buffer sink, long byteCount) throws IOException {
                            try {
                                return super.read(sink, byteCount);
                            } catch (IOException e) {
                                thrownException = e;
                                throw e;
                            }
                        }
                    });
        }

        @Override
        public MediaType contentType() {
            return delegate.contentType();
        }

        @Override
        public long contentLength() {
            return delegate.contentLength();
        }

        @Override
        public BufferedSource source() {
            return delegateSource;
        }

        @Override
        public void close() {
            delegate.close();
        }

        void throwIfCaught() throws IOException {
            if (thrownException != null) {
                throw thrownException;
            }
        }
    }
}
